package cn.felord.common.config;

import cn.felord.common.bean.IdentifierEnum;
import cn.felord.common.bean.Rest;
import cn.felord.common.bean.RestBody;
import cn.felord.common.entity.SysUser;
import cn.felord.common.enumeration.StateEnum;
import cn.felord.common.security.*;
import cn.felord.common.security.event.UrlRolesRefreshEvent;
import cn.felord.common.security.event.UrlRolesRefreshEventPublisherAware;
import cn.felord.common.service.ISysResourcesService;
import cn.felord.common.service.ISysRoleService;
import cn.felord.common.service.ISysUserService;
import cn.hutool.core.util.ArrayUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * The type Web security config.
 *
 * @author Dax
 * @since 19 :28  2018/9/9
 */
@Slf4j
@Configuration
@AutoConfigureAfter(JwtConfig.class)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private ISysUserService iSysUserService;
    @Resource
    private ISysResourcesService iSysResourcesService;
    @Resource
    private ISysRoleService iSysRoleService;
    @Resource
    private JwtTokenGenerator jwtTokenGenerator;
    @Resource
    private JwtTokenStorage jwtTokenStorage;

    @Override
    protected UserDetailsService userDetailsService() {
        return username -> {

            try {
                QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().eq(SysUser::getUserName, username).eq(SysUser::getUserState, StateEnum.ENABLE.value());

                SysUser user = Optional.of(iSysUserService.getOne(queryWrapper)).get();
                String userId = user.getId();

                Optional<Set<String>> optional = Optional.ofNullable(iSysRoleService.selectUserRoleByUserId(userId));
                Set<String> roleNames = optional.orElse(new HashSet<>());
                List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(ArrayUtil.toArray(roleNames, String.class));
                return new SecureUser(userId, username, user.getPassword(), authorities);
            } catch (Exception e) {
                throw new UsernameNotFoundException("user [" + username + "] is not found");
            }

        };
    }


    /**
     * 当角色  uri 关系变动时 刷新到 权限注册表中.
     *
     * @return the url roles refresh event publisher aware
     */
    @Bean
    public UrlRolesRefreshEventPublisherAware eventPublisherAware() {
        UrlRolesRefreshEvent rolesRefreshEvent = new UrlRolesRefreshEvent(this);
        return new UrlRolesRefreshEventPublisherAware(rolesRefreshEvent);
    }

    /**
     * 密码机.
     *
     * @return the password encoder
     */
    @Bean
    @Primary
    public PasswordEncoder passwordEncoder() {
        String seed = "KRYPTON_SEED";
        SecureRandom secureRandom = new SecureRandom(seed.getBytes());
        return new BCryptPasswordEncoder(10, secureRandom);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService());
        //   使用 BCryptPasswordEncoder  不再需要配置盐 配置将抛出异常
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        auth.authenticationProvider(daoAuthenticationProvider);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 自定义访问决策管理器 可配置忽略的 antPath.
     *
     * @return the access decision manager
     */
    @Bean
    public AccessDecisionManager customAccessDecisionManager() {
        return new CustomAccessDecisionManager();
    }

    /**
     * 访问资源路径的 资源角色关系  如果资源存在 但是没有对应角色则 赋予{@link cn.felord.common.security.CustomCodeEnum#UN_ROLE}.
     * 没有返回null
     *
     * @return the filter invocation security metadata source
     */
    @Bean
    public FilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource() {
        return new CustomFilterInvocationSecurityMetadataSource(iSysResourcesService);
    }

    /**
     * Jwt 权限验证过滤器.
     *
     * @return the jwt authentication filter
     * @throws Exception the exception
     */
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        return new JwtAuthenticationFilter(super.authenticationManager());
    }


    /**
     * 自定义拒绝访问处理 返回json.
     *
     * @return the access denied handler
     */

    public AccessDeniedHandler accessDeniedHandler() {
        return (request, response, accessDeniedException) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }

            Map<String, Object> map = new HashMap<>();
            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("uri", request.getAttribute("javax.servlet.error.request_uri"));
            this.responseJsonWriter(response, RestBody.build(HttpServletResponse.SC_FORBIDDEN, map, "U are denied", IdentifierEnum.DISABLE));
        };
    }

    /**
     * 成功登录处理器 处理成功登录后的逻辑 登录成功返回认证信息 以此为依据跳转.
     *
     * @return the authentication success handler
     */

    private AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request, response, authentication) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }
            Map<String, Object> map = new HashMap<>();
            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("flag", "success_login");
            SecureUser principal = (SecureUser) authentication.getPrincipal();
            String userId = principal.getUserId();

            JwtClaimsBuilder jwtClaimsBuilder = new JwtClaimsBuilder();

            JwtClaims jwtClaims = jwtClaimsBuilder
                    .iss("tara")
                    .sub("all")
                    .userId(userId)
                    .expDays(7)
                    .builder();
            JwtClaims refreshJwtClaims = jwtClaimsBuilder
                    .iss("tara")
                    .sub("all")
                    .userId(userId)
                    .expDays(30)
                    .builder();

            String accessToken = jwtTokenGenerator.jwtToken(jwtClaims);
            String refreshToken = jwtTokenGenerator.jwtToken(refreshJwtClaims);
            JwtEntity jwtEntity = new JwtEntity();
            jwtEntity.setAccess_token(accessToken);
            jwtEntity.setRefresh_token(refreshToken);
//            存入缓存
            jwtTokenStorage.put(jwtEntity, userId);

            map.put("access_token", accessToken);
            map.put("refresh_token", refreshToken);
            this.responseJsonWriter(response, RestBody.okData(map, "登录成功"));
        };
    }

    /**
     * 失败登录处理器 处理登录失败后的逻辑 登录失败返回信息 以此为依据跳转
     *
     * @return the authentication failure handler
     */

    private AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }
            Map<String, Object> map = new HashMap<>();

            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("flag", "failure_login");
            this.responseJsonWriter(response, RestBody.failureData(map, "验证失败"));
        };
    }

    /**
     * 成功退出登录 处理  默认地址  /logout.
     *
     * @return the logout success handler
     */

    private LogoutSuccessHandler logoutSuccessHandler() {
        return (request, response, authentication) -> {
            if (response.isCommitted()) {
                log.debug("Response has already been committed");
                return;
            }
            System.out.println(authentication);
            Map<String, Object> map = new HashMap<>();

            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            map.put("flag", "success_logout");
            this.responseJsonWriter(response, RestBody.okData(map, "退出成功"));
        };
    }

    private LogoutHandler jwtLogoutHandler() {
        return (request, response, authentication) -> {
            String header = request.getHeader("Authorization");

            if (header != null && header.startsWith("Bearer ")) {
                String token = header.replace("Bearer ", "");
                JwtClaims jwtClaims = jwtTokenGenerator.decodeAndVerify(token);
                if (Objects.nonNull(jwtClaims)) {
                    String userId = jwtClaims.getAud();
                    jwtTokenStorage.expire(userId);
                }
            }
        };
    }


    private void responseJsonWriter(HttpServletResponse response, Rest rest) throws IOException {
        response.setStatus(HttpServletResponse.SC_OK);
        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        ObjectMapper objectMapper = new ObjectMapper();
        String resBody = objectMapper.writeValueAsString(rest);
        PrintWriter printWriter = response.getWriter();
        printWriter.print(resBody);
        printWriter.flush();
        printWriter.close();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {


        http.cors()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler())
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O filterSecurityInterceptor) {
                        filterSecurityInterceptor.setAccessDecisionManager(wireBean("customAccessDecisionManager", CustomAccessDecisionManager.class));
                        filterSecurityInterceptor.setSecurityMetadataSource(wireBean("customFilterInvocationSecurityMetadataSource", CustomFilterInvocationSecurityMetadataSource.class));
                        return filterSecurityInterceptor;
                    }
                })
                .and()
//                .addFilterBefore(wireBean(JwtAuthenticationFilter.class), UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .successHandler(authenticationSuccessHandler())
                .failureHandler(authenticationFailureHandler())
                .permitAll()
                .and()
                .addFilter(wireBean(JwtAuthenticationFilter.class))
                .logout()
                .addLogoutHandler(jwtLogoutHandler())
                .logoutSuccessHandler(logoutSuccessHandler())
                .clearAuthentication(true)
                .permitAll()
                .and()
//                退出 返回ajax  Get 操作 只能通过 禁用csrf
                .csrf()
                .disable();



/*

        http.exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler())
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O filterSecurityInterceptor) {
                        filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
                        filterSecurityInterceptor.setSecurityMetadataSource(securityMetadataSource());
                        return filterSecurityInterceptor;
                    }
                })
                .and()
                .formLogin()
                .successHandler(authenticationSuccessHandler())
                .failureHandler(authenticationFailureHandler())
                .permitAll()
                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler())
                .clearAuthentication(true)
                .invalidateHttpSession(true)
                .permitAll()
                .and()
//                退出 返回ajax  Get 操作 只能通过 禁用csrf
                .csrf()
                .disable();
*/

    }

    private <T> T wireBean(Class<T> beanType) {
        return super.getApplicationContext().getBean(beanType);
    }

    private <T> T wireBean(String beanName, Class<T> beanType) {
        return super.getApplicationContext().getBean(beanName, beanType);
    }
}
